package com.novartis.nims.imagedc.application;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

import java.awt.Dimension;
import javax.swing.BoxLayout;
import javax.swing.JButton;

import java.awt.CardLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.FontMetrics;
import java.awt.KeyboardFocusManager;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;

import javax.swing.border.BevelBorder;

import com.novartis.nims.imagedc.util.HtmlJPanel;

public class ApplicationView implements Runnable {
	
	private static InputDirectoryPanel inputDirectoryPanel = new InputDirectoryPanel();
	private static ScanReviewPanel scanReviewPanel = new ScanReviewPanel();
	private static SubmissionResultsPanel submissionResultsPanel = new SubmissionResultsPanel();
	
	private static enum Card {
		DIRECTORY_INPUT("Directory input panel", inputDirectoryPanel),
		SCAN_REVIEW("Scan review panel", scanReviewPanel),
		SUBMISSION_RESULTS("Submission results panel", submissionResultsPanel);
		
		private final String name;
		private final JPanel panel;
		
		private Card(String theName, JPanel thePanel) {
			name = theName;
			panel = thePanel;
		}
		
		private String getName() {
			return name;
		}
		
		private JPanel getPanel() {
			return panel;
		}
		
		private Card next() {
			return values()[Math.min(ordinal() + 1, values().length - 1)];
		}
		
		private Card previous() {
			return values()[Math.max(ordinal() - 1, 0)];
		}
		
		private boolean isFirst() {
			if (ordinal() == 0) return true; else return false;
		}
		
		private boolean isLast() {
			if (ordinal() == values().length - 1) return true; else return false;
		}
	}
	
	private ApplicationController controller = null;
	
	private JFrame frame;
	private JPanel contentPanel;
	private JPanel cardPanel;
	private JPanel navigationPanel;
	private JButton nextButton;
	private JButton backButton;
	
	private ActionListener nextActionListener = new NextCardActionListener();
	private ActionListener previousActionListener = new PreviousCardActionListener();
	
	private Card currentCard = Card.values()[0];
	
	public ApplicationView() {
		frame = new JFrame();
		frame.setTitle("ImagEDC");
		frame.addWindowListener(new WindowAdapter() {
			@SuppressWarnings("unused")
			public void WindowClosing(WindowEvent we) {
				close();
			}
		});
		
		contentPanel = new JPanel();
		contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
		contentPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
		
		cardPanel = new JPanel();
		cardPanel.setLayout(new CardLayout());
		
		navigationPanel = new JPanel();
		navigationPanel.setLayout(new FlowLayout(FlowLayout.TRAILING));
		navigationPanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
		
		nextButton = new JButton("Next");
		nextButton.addActionListener(nextActionListener);
		backButton = new JButton("Back");
		backButton.addActionListener(previousActionListener);
		
		frame.setContentPane(contentPanel);
		contentPanel.add(cardPanel);
		contentPanel.add(navigationPanel);
		for (Card card : Card.values()) {
			cardPanel.add(card.getPanel(), card.getName());
		}
		navigationPanel.add(backButton);
		navigationPanel.add(nextButton);
	}

	public void run() {
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		}
		catch (ClassNotFoundException ignore) {}
		catch (InstantiationException ignore) {}
		catch (IllegalAccessException ignore) {}
		catch (UnsupportedLookAndFeelException ignore) {}
		
		first();
		frame.pack();
		frame.setVisible(true);
	}
	
	void setController(ApplicationController theController) {
		controller = theController;
	}

	void updateTableModel(ScanTableModel model) {
		scanReviewPanel.updateTableModel(model);
	}
	
	void clearTable() {
		scanReviewPanel.clearTable();
	}
	
	private void close() {
		System.exit(0);
	}
	
	void first() {
		((CardLayout)cardPanel.getLayout()).first(cardPanel);
		currentCard = Card.values()[0];
		
		nextButton.setText("Next");
		backButton.setText("Back");
		backButton.setEnabled(false);
	}
	
	void next() {
		((CardLayout)cardPanel.getLayout()).next(cardPanel);
		currentCard = currentCard.next();
		backButton.setEnabled(true);
		
		if (currentCard.isLast()) {
			backButton.setText("Restart");
			nextButton.setText("Close");
		}
	}
	
	void previous() {
		((CardLayout)cardPanel.getLayout()).previous(cardPanel);
		currentCard = currentCard.previous();
		
		if (currentCard.isFirst()) {
			backButton.setEnabled(false);
		}
	}
	
	void maximize() {
		frame.pack();
		frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
	}
	
	void pack() {
		frame.pack();
	}
	
	private class NextCardActionListener implements ActionListener {

		public void actionPerformed(ActionEvent e) {
			if (currentCard.getPanel() instanceof InputDirectoryPanel) {
				InputDirectoryPanel panel = (InputDirectoryPanel)currentCard.getPanel();
				
				if (!panel.isInputValid() || KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner() != nextButton) {
					showErrorMessage("Please select an input directory and enter a valid Study ID before continuing.");
					return;
				}
				
				controller.loadAndIdentifyScans(
						panel.getSelectedDirectory(),
						panel.getStudyId()
				);
			} else if (currentCard.getPanel() instanceof ScanReviewPanel) {
				ScanReviewPanel panel = (ScanReviewPanel)currentCard.getPanel();
				
				if (!panel.isInputValid()) {
					showErrorMessage("Please fill all required fields before continuing.");
					return;
				}
				
				controller.updateAndSubmitScans(true);
			} else if (currentCard.isLast()) {
				close();
			}
		}
	}
	
	private class PreviousCardActionListener implements ActionListener {

		public void actionPerformed(ActionEvent e) {
			if (currentCard.isLast()) {
				first();
			} else if (currentCard.getPanel() instanceof ScanReviewPanel) {
				controller.clearScans();
				clearTable();
				previous();
			}
			frame.pack();
		}
	}
	
	void registerWorker(SwingWorker<Void, Void> worker) {
		ApplicationProgressMonitor Monitor = new ApplicationProgressMonitor(
				worker,
				frame,
				"Adding subjects to trial submission.",
				"", 0, 100);
		Monitor.setMillisToPopup(0);
		Monitor.setMillisToDecideToPopup(0);
	}
	
	private class ApplicationProgressMonitor extends ModalProgressMonitor implements PropertyChangeListener {
		private SwingWorker<Void, Void> Worker;
		private boolean FinishedWorkerIsProcessed = false;
		
		private ApplicationProgressMonitor(SwingWorker<Void, Void> worker, Component parentComponent, Object message, String note, int min, int max) {
			super(parentComponent, message, note, min, max);
			Worker = worker;
			Worker.addPropertyChangeListener(this);
		}

		public void propertyChange(PropertyChangeEvent evt) {
			if ("progress" == evt.getPropertyName()) {
				if (!Worker.isDone()) {
					if (isCanceled()) {
						// if user has canceled, then cancel the worker
						Worker.cancel(true);
					} else { //otherwise update the progress monitor
						int progress = (Integer) evt.getNewValue();
						setProgress(progress);
					}
				}
				
	        } else if ("message" == evt.getPropertyName()) {
        		String message = (String) evt.getNewValue();
        		setNote(message);
        		
	        } else if ("determinate" == evt.getPropertyName()) {
	        	boolean determinate = (Boolean) evt.getNewValue();
	        	setIndeterminate(!determinate);
	        	
        	} else if ("state" == evt.getPropertyName()) {
	        	if (Worker.isDone() && !(FinishedWorkerIsProcessed)) {
	        		FinishedWorkerIsProcessed = true;
	        		
	                Toolkit.getDefaultToolkit().beep();
	                
	                try {
	                	Worker.get();
//	                	logger.info("Task execution finished successfully.");
	                } catch (CancellationException e) {
//						logger.info("Execution finished due to user cancelation.");
	                } catch (InterruptedException e) {
	                	// do nothing
	                } catch (ExecutionException e) {
						Throwable cause = e.getCause();
						if (cause == null) cause = e;
						
						String message = cause.toString();
						
						StringWriter sw = new StringWriter();
						PrintWriter pw = new PrintWriter(sw);
						e.printStackTrace(pw);
						pw.flush();
//						logger.error(sw.toString());
						pw.close();
//						logger.info("Execution finished due to error.");

						showErrorMessage(message);
                	} finally {
                		close();
                	}
	        	}
	        }
		}
	}
	
	void showErrorMessage(String message) {
		Object feedback = message;
		
		HtmlJPanel messagePane = new HtmlJPanel(message);
		
		String osName = System.getProperty("os.name");
		
		if (osName.contains("Windows")) {
			int maxWidth = (int) frame.getPreferredSize().getWidth();
			FontMetrics fm = messagePane.getFontMetrics(messagePane.getFont());
			int numberOfLines = 10;
			int height = (numberOfLines) * fm.getHeight();

			messagePane.setPreferredSize(new Dimension(maxWidth, height));
			messagePane.scrollToTop();
			messagePane.scrollToLeft();
			feedback = messagePane;
		}
		
		JOptionPane.showMessageDialog(frame,
				feedback,
				"Error",
				JOptionPane.ERROR_MESSAGE);
	}

}
